<?php
// +----------------------------------------------------------------------
// | 杭州十禾信息科技有限公司
// +----------------------------------------------------------------------
// | Copyright (c) 2013-2015 http://www.shelpe.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: LeePuman <ysyzlsm@163.com>
// +----------------------------------------------------------------------

/**
 * 自动开车服务. 根据用户选择的商品, 自动选择关键词及价格进行投放,
 * 并且根据效果自动增删改关键词.
 */
class AutodriveAction extends ClientAction {
	
	/** @var ProductQueueModel */
    private $_autoProductQueueMdl = null;
    /** @var Model */
    private $_autoKeywordLogMdl= null;
    /** @var KeywordModel */ 
	private $_keywordMdl = null;
	/** @var ProductModel */
	private $_productMdl = null;
	/** @var AutoDriveBaseModel */
	private $_autoDriveMdl = null;

	/**
	 * 给推广组自动设标题
	 */
	public function maketitle()
	{
		$prodMdl = new ProductModel(); // 新建一个ProductModel实例
		$prodId = 25641828240; // 目标产品标题
		$product = $prodMdl->getProductById($prodId, 'C', 'title,cid,props_name'); // 获取必要的产品信息
		
		// 示例一：获取产品原始属性
		$tmpProps = explode(';', $product['props_name']);
		// $tmpProps = explode(';', $tmpProps['props_name']);
		$prodProps = array();
		foreach($tmpProps as $prop) {
			$propKeyValues = explode(':', $prop); // 2915574:11292600:板型:蝙蝠型
			$prodProps[$propKeyValues[2]] = $propKeyValues[3];
		}
		echo '示例一：获取产品原始属性';
		dump($prodProps); // 测试。打印结果
		
		// 示例二：获取过滤过后的属性
		$prodProps = $prodMdl->getProductProps($prodId); // 获取产品的属性
		echo '示例二：获取过滤过后的属性';
		dump($prodProps); // 测试。打印结果
		
		$cateMdl = new CategoryModel();
		// 示例三：猜测产品的类型
		$category = $product['cat_name'] ?  $product['cat_name']
				: (!empty($product['cid']) ? $cateMdl->getCateName($product['cid']) : '');
		$productType = zh_strtolower($prodMdl->guessProductType($product['title'], $category));
		echo '示例三：猜测产品的类型';
		dump($productType);
		
		// 示例四：调用API获取产品所在类目的热门关键词
		// $cateHotWords = $cateMdl->where(array('id'=>$product[]))
		$apiMdl = new ApiModel();
		$cateHotWords = $apiMdl->getCateHotWords(array($product['cid']));
		echo '示例四：调用API获取产品所在类目的热门关键词';
		dump($cateHotWords);
	}
	
    /**
     * 读取用户提交的产品, 自动选择关键词及价格进行投放.
     * 
     * @return 当全部成功时, 返回true, 否则返回false. 如果是通过Carine调用,
     *  全部成功时输出'true', 否则输出'false'. 
     */
	public function start()
	{
		// if items specified, init these items, otherwise init all new items
		$isCarine = $this->_request('is_carine') == 1 ? true : false;
		$isSync = $this->_request('is_sync') == 1 ? true : false;
		$items = $this->_request('items') ? explode(',', $this->_request('items')) : null;
		$custom_default_price = $this->_request('custom_default_price');
		$custom_default_price = (int) $custom_default_price > 0 ? (int) $custom_default_price : 0;
		$todoProducts = $this->_getProductQueueMdl()->getTodoProducts($items);
		
		if($isCarine && !$isSync) { // 异步的话直接返回
			echo $todoProducts ? 'true' : 'false';
			my_fastcgi_finish_request();
		}
		
		$this->logInfo(count($todoProducts) . ' auto drive products to start');
		if(empty($todoProducts)) {
			if($isCarine && $isSync) // 通过Carine调用时, 如果没有找到产品, 相当于失败.
				echo 'false';
			return true; // 脚本执行时, 没有产品正常, 返回true.
		}
		
		$succ = true;
		$adgroupMdl = new AdgroupModel();
		$userMdl = new UserModel();
		foreach ($todoProducts as $pqProduct) {
			$this->logInfo('初始化自动开车. 产品: ' . $pqProduct['product_id'] . '; 推广计划: ' . $pqProduct['campaign_id']);
			
			$adgroupId = 0;
			$currSucc = true;
			$failMsg = '';
			try {

				// 根据产品卖家设定session!!!
				$userMdl->setAuth($pqProduct['user_id']);
				
				// set flag
				$setUpdatingSucc = $this->_getProductQueueMdl()->updateProgress($pqProduct['product_id'], $pqProduct['campaign_id'], 0, ProductQueueStatus::UPDATING);
				if(!$setUpdatingSucc) // 有两种情况: 1) 记录已经被删除; 2) 另外一个进程正在更新该产品
					continue; 
				
				// 检查产品数据是否完整
				if(!$this->_getProductQueueMdl()->checkProduct($pqProduct)) {
					$currSucc = false;
					$failMsg = '没有产品' . (!$pqProduct['title1'] ? '标题.' :  '图片.');
				}

				// 根据strategy创建自动开车模型实例
				$autoDriveMdl = $this->_getAutoDriveMdl($pqProduct['strategy']); //new $autoDriveMdlName();
				
				// create adgroup
				Log::record("creating adgroup...", Log::DEBUG);
				if($currSucc) {
					if($custom_default_price > 0)
						$pqProduct['default_price'] = $custom_default_price;
					$adgroupId = $autoDriveMdl->createAutoAdgroup($pqProduct);
					Log::record("$adgroupId: $adgroupId", Log::DEBUG);
					if(!$adgroupId) {
						$currSucc = false;
						$failMsg = '创建推广组失败.';
					}
				}
				
				// 创建catematch
				
				if($currSucc) {
					Log::record("init autodrive model...", Log::DEBUG);
					$autoDriveMdl->init($adgroupId, $pqProduct['user_id']);
					//为啥还要
					/* catmatch取消了
					try{
						Log::record("setup catmatch...", Log::DEBUG);
						$currSucc = $autoDriveMdl->setCatmatchPrice();
					}catch( Exception $e){
						$this->logException($e , true);
						$currSucc = false;
						$failMsg = '设置类目出价失败.';
					}
					*/
				}
				
				// set up keywords
				if($currSucc) {
					// set adgroup status
					if($pqProduct['need_confirm']) {
						Log::record("pausing adgroup...", Log::DEBUG);
						$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_WAITING_CONFIRM);
						$adgroupMdl->pauseOrOpenAdgroup(
								$pqProduct['user_id'], $adgroupId, 'offline', true);
					} else {
						Log::record("updating status ...", Log::DEBUG);
						$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_UPDATING);
					}
					
					Log::record("updating progress as 20%...", Log::DEBUG);
					$pqProduct['adgroup_id'] = $adgroupId;
					$this->_getProductQueueMdl()->updateProgress($pqProduct['product_id'], $pqProduct['campaign_id'], 20, ProductQueueStatus::UPDATING);
					
					// Do the real job. find keywords, set the price, and submit them by API
					Log::record("adding keywrods...", Log::DEBUG);
					$resAddWords = $autoDriveMdl->addProKeywords(null, true, 0, $custom_default_price);
					
					if($resAddWords !== true) {
						$failMsg = '添加关键词失败: ' . $resAddWords;
						$currSucc = false;
					}
				}
				
				// handle results
				if(!$currSucc) {
					$this->_getProductQueueMdl()->updateProgress($pqProduct['product_id'], $pqProduct['campaign_id'], 
							-1, ProductQueueStatus::FAILED, $failMsg);
				} else {// if failed, update the status as ProductQueueStatus::FAILED
					$this->_getProductQueueMdl()->updateProgress($pqProduct['product_id'], $pqProduct['campaign_id'], 
							100, ProductQueueStatus::SUCCEEDED);
				}
				if(!$pqProduct['need_confirm'] && $adgroupId) { // 成功
					$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_IDLE, AdgroupModel::STATUS_UPDATING, true);
				}

				$succ = $succ && $currSucc;
				
			} catch (Exception $e) {
				Log::record('Exception caught: ' . $e->getTraceAsString());
				// log the exception details 
				$this->logException($e, true);
				
				$errMsg = getTopError($e->getMessage());
				if(!$errMsg) $errMsg = '内部错误';
				$this->_getProductQueueMdl()->updateProgress($pqProduct['product_id'], $pqProduct['campaign_id'], 
						-1, ProductQueueStatus::FAILED, $errMsg);
				if($adgroupId) { // 失败
					$adgroupMdl->updateStatus($adgroupId, 
							$pqProduct['need_confirm'] ? AdgroupModel::STATUS_WAITING_CONFIRM : AdgroupModel::STATUS_IDLE, 
							AdgroupModel::STATUS_UPDATING);
				}
				
				$succ = false;
				
				continue;
			}
			sleep(1);
		}
		
		if($isCarine && $isSync)
			echo $succ ? 'true' : 'false';
		return $succ;
	}

	/**
	 * 维护已经提交的自动投放的关键词.
	 * 如果测试的话，需要设3个参数：
	 * 	test = 1 [must]
	 * 	user = NNN [user id]
	 * 	adgroup = NNN [adgroup id]
	 * 	strategy = XX ['Base', 'V2', ...]
	 */
	public function update()
	{
		if(defined('CLI_MODE') || (isset($_GET['test']) && $_GET['test'] == 1)){
			$campaignMdl = new CampaignModel();
		
			$isTest = isset($_GET['test']) && $_GET['test'] == 1 ? true : false;
			
			$userMdl = new UserModel();
			$adgroupMdl = new AdgroupModel();
			
			$RptMdl = new RptCampaignModel();
			
			$userIds = $adgroupMdl->getValidAutoUserIds(min(AutoDriveBaseModel::$MAINTAIN_PERIOD)); // $userMdl->getValidUserIds(true);

			if($isTest) {
				if(isset($_GET['user']))
					$userIds = array($_GET['user']);
			}
			$isNoSubmit = isset($_GET['nosubmit']) && $_GET['nosubmit'] == 1 ? true : false;
			$autoDrvMdl = $this->_getAutoDriveMdl( DEFAULT_AUTO_ENGINE);
			$ret = true;
			if($userIds) {
				foreach($userIds as $userId) {
					$this->logInfo("User: $userId");
					
					// 先最大范围选出可能需要维护的推广组，具体是否需要维护在循环里面还要判断一次。
					$adgroups = $isTest && isset($_GET['adgroup'])
							? array($adgroupMdl->getAdgroupByAdgroupId($userId, $_GET['adgroup']))
							: $adgroupMdl->getAutodriveAdgroups($userId);
					if(!$adgroups)
						continue;
					
					// set session & oauth
					try {
						$userMdl->setAuth($userId);
					} catch(Exception $e) {
						$this->logError('验证用户权限失败。错误：'. $e->getMessage());
						if($e->getCode() != 403) 
							$this->logException($e, true);
						$ret = false;
						continue;
					}

					$keywordMdl=new KeywordModel();
					$wordsToUpdates=array();
					foreach($adgroups as $adgroup) {
						$adgroupId = $adgroup['adgroup_id'];
						//// 检查推广计划是否开启
		        		$campaign = $campaignMdl->getCampaignById($userId , $adgroup['campaign_id'],false);
		        		if($campaign["campaign"]['online_status']!='online'){
		        			continue;
		        		}
		        		
						$pqProduct = $this->_getProductQueueMdl()->getItem($adgroup['num_iid'], $adgroup['campaign_id']);
						/*	$autoDrvMdl = $this->_getAutoDriveMdl(isset($_GET['strategy']) ? $_GET['strategy'] // TODO 这个是用来测试的，需要删除。
											: ($pqProduct ? $pqProduct['strategy'] : DEFAULT_AUTO_ENGINE));*/
						// 检查是否达到更新周期
						if($autoDrvMdl::$MAINTAIN_PERIOD[$pqProduct['type']] * 86400
								+ strtotime($adgroup['maintained_date']) 
								> date2time())
							continue;
						//检查是否预算充足
						if($pqProduct['type']==1){
							$stat = $RptMdl->getAllCampaignsStat($adgroup['campaign_id'] , 'cost,impressions' , date('Y-m-d', strtotime('-2 day')),date('Y-m-d', strtotime('-1 day')) ,'series');
							// $campaign = $campaignMdl->getCampaignById($userId ,$adgroup['campaign_id']  , false);
							$this->logInfo("impression:". $stat['series']['展现量'][1][1]);
							if($stat['series']['展现量'][1][1]>0){//判断前一天是否有数据，如果没有则不更改预算
								$cost=max($stat['series']['花费'][0][1],$stat['series']['花费'][1][1]);//$campaign['budget'];
								$exp=pow(0.5,$cost/$campaign['campaign']['budget'])-0.5;//确定当前出价参数应提高的倍数，小于0.05时不用提高
								$condition['campaign_id']=$adgroup['campaign_id'];
								$condition['user_id']=$userId;
								$condition['product_id']=$adgroup['num_iid'];
								$this->logInfo("exp:". $exp);
								if($exp>0.05) {
									$exp = round($exp ,2);
									$this->logInfo("exp:". $exp);
									if($pqProduct['max_expense']>0){
										$pqProduct['max_expense']=$pqProduct['max_expense']*(1+$exp)<=200?$pqProduct['max_expense']*(1+$exp):200;
										$succ = M("auto_pro_product_queue")->where($condition)->save(array('max_expense'=>$pqProduct['max_expense']));
									}else{
										$pqProduct['max_expense']=$exp*100+100;
										$this->logInfo("exp:". $pqProduct['max_expense']);
										$succ = M("auto_pro_product_queue")->where($condition)->save(array('max_expense'=>$pqProduct['max_expense']));
									}
								} elseif($pqProduct['max_expense']>0) {
									$pqProduct['max_expense']=($pqProduct['max_expense']-10)>105?($pqProduct['max_expense']-10):0;
									$succ = M("auto_pro_product_queue")->where($condition)->save(array('max_expense'=>$pqProduct['max_expense']));
								}
							}
						}
						
						$this->logInfo("Adgroup: $adgroupId");
						// 自动提交关键词的时候也要更新其状态
						$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_UPDATING);

						try {
							$wordsToUpdate=$autoDrvMdl->init($adgroup, $userId, $pqProduct)
									->setDriveParams(array('test' => $isTest && $isNoSubmit))
									->maintainAdgroupKeywords();
							if($wordsToUpdate){
							$wordsToUpdates=array_merge($wordsToUpdates,$wordsToUpdate);
							} 
							// dump(memory_get_usage());
							$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_IDLE, 
									AdgroupModel::STATUS_UPDATING, true);
							
						} catch (Exception $e) {
							// log the error
							$this->logException($e, true);
							if(strpos($e->getMessage(),'session-expired')){
								break;
							}
							$ret = false;
							
							$adgroupMdl->updateStatus($adgroupId, AdgroupModel::STATUS_IDLE,
									AdgroupModel::STATUS_UPDATING, false);
							
							if($isTest)
								throw $e;
						}
						
						if($isTest) // 测试一个adgroup即可
							throw_exception('测试一个adgroup完成. 故意抛出异常.');
						
						// 如果没有要更新的任务，返回'no_more'告诉脚本停止。
						if(!$userIds)
							return 'no_more';
						
						usleep(1500000); // 休眠1.5s，防止API调用过快
					}
					
					//集中更改需要改价的关键词
					$num=180;
					$updateArrays=array_chunk($wordsToUpdates,$num);
					foreach($updateArrays as $updateArray){
						$keywordMdl->updateKeywords(0, $updateArray, null, true);
					}
					sleep(1); // 休眠2s，防止API调用过快 
				}
			}
			return $ret;
		}
	}
	
	/**
	 * @return ProductQueueModel
	 */
	private function _getProductQueueMdl() {
		if ($this->_autoProductQueueMdl === null) {
			$this->_autoProductQueueMdl = new ProductQueueModel();
		}
		return $this->_autoProductQueueMdl;
	}

	/**
	 * @return Model
	 */
	private function _getKeywordLogMdl() {
		if ($this->_autoKeywordLogMdl === null) {
			$this->_autoKeywordLogMdl = M('auto_pro_keyword_log');
		}
		return $this->_autoKeywordLogMdl;
	}

	/**
	 * @return KeywordModel
	 */
	private function _getKeywordMdl() {
		if ($this->_keywordMdl === null) {
			$this->_keywordMdl = new KeywordModel();
		}
		return $this->_keywordMdl;
	}
	
	/**
	 * @return ProductModel
	 */
	private function _getProductMdl() {
		if($this->_productMdl === null) {
			$this->_productMdl = new ProductModel();
		}
		return $this->_productMdl;
	}
	
	/**
	 * @param string $strategy='Base'
	 * @return AutoDriveBaseModel
	 */
	private function _getAutoDriveMdl($strategy = DEFAULT_AUTO_ENGINE) {
		$autoDriveMdlName = 'AutoDrive' . $strategy . 'Model';
		if($this->_autoDriveMdl == null || get_class($this->_autoDriveMdl) != $autoDriveMdlName) {
			$this->_autoDriveMdl = new $autoDriveMdlName();
		}
		return $this->_autoDriveMdl;
	}
}
